package com.chenfan.filestore.service;

import cn.afterturn.easypoi.word.WordExportUtil;
import cn.hutool.system.OsInfo;
import cn.hutool.system.SystemUtil;
import com.alibaba.fastjson.JSONObject;
import com.aspose.words.Document;
import com.aspose.words.FontSettings;
import com.aspose.words.License;
import com.aspose.words.SaveFormat;
import com.chenfan.ccp.common.exception.ApiException;
import com.chenfan.filestore.dto.WordToPdfDTO;
import com.chenfan.filestore.exception.FileStoreCode;
import com.chenfan.filestore.model.MinioMetaInfo;
import com.chenfan.filestore.repository.MinioTemplate;
import com.chenfan.filestore.utils.Base64Util;
import com.chenfan.filestore.utils.FileUtil;
import com.chenfan.filestore.utils.PDFMergerUtil;
import com.chenfan.filestore.utils.ToStringUtil;
import com.chenfan.filestore.vo.FileResult;
import com.github.geko444.im4java.core.ConvertCmd;
import com.github.geko444.im4java.core.IMOperation;
import com.github.geko444.im4java.process.Pipe;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.tomcat.util.codec.binary.Base64;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import sun.misc.BASE64Encoder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static com.chenfan.filestore.utils.TimeThreadSafeUtils.YYYY_MM_DD_HH_MM_SS;

/**
 * @author SXR
 * @date 2018/7/12
 */
@Service
@Slf4j
public class FileService extends AbastractFileService {

    private static final String[] FILE_TYPES = {"jpg", "bmp", "jpeg", "png", "gif", "tif", "pcx", "tga", "exif", "exif", "svg", "psd", "cdr", "pcd", "dxf", "ufo", "eps", "ai", "raw", "WMF", "webp"};
    private static final Logger LOGGER = LoggerFactory.getLogger(FileService.class);
    private AtomicInteger atomicInteger = new AtomicInteger(0);

    @Autowired
    private MinioTemplate minioTemplate;

    /**
     * 批量上传文件
     *
     * @param request 文件请求
     * @return 文件上传对象
     */
    public List<FileResult> uploadFiles(MultipartHttpServletRequest request) {
        return uploadFiles(request,MinioTemplate.DEFAULT_BUCKETNAME,null);
    }

    public FileResult uploadFile(File file) throws Exception {
        FileResult fileResult = new FileResult();
        final FileInputStream fileInputStream = new FileInputStream(file);
        try {
            String path = file.getAbsolutePath();
            String uploadPath2 = FileUtil.getUploadPath(path);
            Map<String, String> headerMap = new HashMap<>();
            URLConnection connection = file.toURL().openConnection();
            //增加模板文件缓存
            connection.setUseCaches(true);
            headerMap.put("Content-Type", connection.getContentType());
            headerMap.put(MinioMetaInfo.ORIGNAL_FILE_NAME, Base64Util.encode(file.getName()));
            getMinioClient().putObject(MinioTemplate.DEFAULT_BUCKETNAME, uploadPath2,fileInputStream, file.length(), headerMap);
            fileResult.setFileName(file.getName());
            fileResult.setId(uploadPath2);
            LOGGER.info("上传后文件路径:{},原始文件名:{}",uploadPath2,file.getName());
        } catch (IOException e) {
            LOGGER.error(e.getMessage());
        }finally {
            fileInputStream.close();
        }
        return fileResult;
    }


    public void removeFiles(String... filePaths) throws Exception {
        for (int i = 0; i < filePaths.length; i++) {
            if (StringUtils.isNotEmpty(filePaths[i])) {
                getMinioClient().removeObject(MinioTemplate.DEFAULT_BUCKETNAME, filePaths[i]);
            }
        }
    }

    /**
     * 文件上传
     * @param request
     * @param bucketName 桶名
     * @param uploadPath 指定单个文件格式
     * @return
     * @throws Exception
     */
    public ArrayList<FileResult> uploadFiles(MultipartHttpServletRequest request,String bucketName,String uploadPath) {
        ArrayList<FileResult> result = new ArrayList<>();
        Iterator<String> fileNames = request.getFileNames();
        while (fileNames.hasNext()) {
            String fileName = fileNames.next();
            List<MultipartFile> files = request.getFiles(fileName);
            if(!CollectionUtils.isEmpty(files)){
                files.forEach(file -> {
                    String originalFilename = getOriginalFileName(file);
                    try {
                        String uploadPath2 = Objects.isNull(uploadPath) ? FileUtil.getUploadPath(originalFilename) : uploadPath;
                        if(!uploadPath2.matches("\\d{4}/\\d{2}/\\d{2}/\\w{32}.[a-zA-Z0-9]+")){
                            throw new ApiException(FileStoreCode.FILE_PATH_NOT_RIGHT);
                        }
                        Map<String, String> headerMap = new HashMap<>();
                        headerMap.put("Content-Type", file.getContentType());
                        headerMap.put(MinioMetaInfo.ORIGNAL_FILE_NAME, Base64Util.encode(originalFilename));
                        getMinioClient().putObject(Objects.isNull(bucketName)? MinioTemplate.DEFAULT_BUCKETNAME:bucketName , uploadPath2, file.getInputStream(), file.getSize(), headerMap);
                        FileResult fileResult = new FileResult();
                        fileResult.setFileName(originalFilename);
                        fileResult.setId(uploadPath2);
                        result.add(fileResult);
                        LOGGER.info("上传后文件路径:{},原始文件名:{}",uploadPath2,originalFilename);
                    }catch (ApiException e){
                        throw e;
                    }catch (Exception e) {
                        LOGGER.error(e.getMessage());
                        throw new ApiException(FileStoreCode.FILE_FORCE_UPLOAD_FAIL);
                    }
                });
            }
        }
        return result;
    }

    /**
     * 读取图片
     *
     * @param filePath   文件ID
     * @param request  压缩参数
     * @param response 返回体
     */
    public void viewImg(String filePath, HttpServletRequest request, HttpServletResponse response)
            throws IOException{
        String resize = request.getParameter("resize");
        if (null != request.getAttribute("resize")) {
            resize = request.getAttribute("resize").toString();
        }
        MinioMetaInfo metaInfo = checkAndGetFileInfo(filePath);
        OutputStream out = response.getOutputStream();
        String sub = filePath.substring(filePath.lastIndexOf(".") + 1);
        boolean flag = false;
        for (String fileType : FILE_TYPES) {
            if (sub.equalsIgnoreCase(fileType)) {
                flag = true;
                break;
            }
        }
        //进入预览
        if (flag) {
            viewImg(response, metaInfo, out, resize);
        }
        //进入下载
        else {
            download(response, metaInfo);
        }
    }


    private void viewImg(HttpServletResponse response, MinioMetaInfo metaInfo, OutputStream out, String resize) throws IOException {
        response.setHeader("Content-disposition", "inline; filename=\"" + metaInfo.getOrignalName()+ "\"");
        response.setContentType(metaInfo.getContentType());
        // 不进行压缩的文件大小，单位为bit
        int notCompressionBit = 5120;

        InputStream in = null;
        try {
            in = getMinioClient().getObject(MinioTemplate.DEFAULT_BUCKETNAME, metaInfo.getName());
            if (resize != null && metaInfo.getLength() > notCompressionBit) {
                IMOperation op = new IMOperation();
                op.addImage("-");

                ConvertCmd convert = new ConvertCmd(true);
                convert.setInputProvider(new Pipe(in, null));
                convert.setOutputConsumer(new Pipe(null, out));

                op.autoOrient();
                op.resize(200, 200);
                op.quality(30d);

                if (metaInfo.getContentType().equalsIgnoreCase("image/png")) {
                    op.addImage("png:-");
                } else {
                    op.addImage("jpeg:-");
                }

                try {
                    convert.run(op);
                    LOGGER.info("图片={}加载成功", metaInfo.getName());
                } catch (Exception e) {
                    LOGGER.info("图片={}加载失败", metaInfo.getName(), e);
                    e.printStackTrace();
                }

            } else {
                IOUtils.copy(in, out);
            }
            out.flush();
            out.close();
        } catch (IOException e) {
            LOGGER.error("文件预览出错,退出方法");
        } finally {
            closeOut(out);
            closeIn(in);
        }
        LOGGER.info("查看文件传输完成,退出方法");
    }

    /**
     * 批量下载压缩文件
     *
     * @param zipName      压缩包名称
     * @param fileIdsArray 文件IDS
     * @param response     返回流
     */
    public void downloadToZip(String zipName, String[] fileIdsArray, HttpServletRequest request, HttpServletResponse response) throws IOException {
        //根据不同浏览器处理下载名称
        zipName = dealZipNameByUserAgent(zipName, request);
        // 初始化信息
        response.setContentType("application/zip");
        response.setHeader("Content-disposition", "attachment; filename=\"" + zipName+"\"");
        ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream());
        // 获取文件信息
        Set<String> fileSet = new HashSet<>();
        for (int i = 0; i < fileIdsArray.length; i++) {
            MinioMetaInfo minioMetaInfo = checkAndGetFileInfo(fileIdsArray[i]);
            ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
            IOUtils.copy(getMinioClient().getObject(MinioTemplate.DEFAULT_BUCKETNAME, minioMetaInfo.getName()), byteOut);
            String orignalName = minioMetaInfo.getOrignalName();
            if (fileSet.contains(orignalName)) {
                int index = orignalName.lastIndexOf(".");
                String fileName = orignalName.substring(index - 1);
                String suffix = orignalName.substring(index);
                orignalName = fileName + System.currentTimeMillis() + suffix;
            }
            fileSet.add(orignalName);
            byte[] data = byteOut.toByteArray();
            zipOut.putNextEntry(new ZipEntry(orignalName));
            zipOut.write(data);
            zipOut.closeEntry();
            byteOut.close();
        }
        zipOut.flush();
        zipOut.close();

        LOGGER.info("文件传输完成,退出方法");
    }

    /**
     * 定制压缩包内外文件名下载
     *
     * @param zipName
     * @param files
     * @param request
     * @param response
     * @throws IOException
     */
    public void downloadToZip(String zipName, List<FileResult> files, HttpServletRequest request, HttpServletResponse response) throws IOException {
        //根据不同浏览器处理下载名称
        zipName = dealZipNameByUserAgent(zipName, request);
        // 初始化信息
        response.setContentType("application/zip");
        response.setHeader("Content-disposition", "attachment; filename=\"" + zipName+"\"");
        ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream());
        // 获取文件信息
        Set<String> fileSet = new HashSet<>();
        for (int i = 0; i < files.size(); i++) {
            FileResult file = files.get(i);
            MinioMetaInfo minioMetaInfo = checkAndGetFileInfo(file.getId());
            ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
            IOUtils.copy(getMinioClient().getObject(MinioTemplate.DEFAULT_BUCKETNAME, minioMetaInfo.getName()), byteOut);
            String orignalName = StringUtils.isEmpty(file.getFileName()) ? minioMetaInfo.getOrignalName() : file.getFileName();

            if (fileSet.contains(orignalName)) {
                int index = orignalName.lastIndexOf(".");
                String fileName = orignalName.substring(index - 1);
                String suffix = orignalName.substring(index);
                orignalName = fileName + System.currentTimeMillis() + suffix;
            }
            fileSet.add(orignalName);
            byte[] data = byteOut.toByteArray();
            zipOut.putNextEntry(new ZipEntry(orignalName));
            zipOut.write(data);
            zipOut.closeEntry();
            byteOut.close();
        }
        zipOut.flush();
        zipOut.close();

        LOGGER.info("文件传输完成,退出方法");
    }

    private String dealZipNameByUserAgent(String zipName, HttpServletRequest request) throws UnsupportedEncodingException {
        zipName = new String(zipName.getBytes("ISO8859-1"), StandardCharsets.UTF_8); // 转换文件名，防止乱码
        // 获得请求头中的User-Agent
        String agent = request.getHeader("User-Agent");
        // 根据不同的客户端进行不同的编码
        if (agent.contains("MSIE")) {
            // IE浏览器
            zipName = URLEncoder.encode(zipName, "utf-8");
            zipName = zipName.replace("+", " ");
        } else if (agent.contains("Firefox")) {
            // 火狐浏览器
            BASE64Encoder base64Encoder = new BASE64Encoder();
            zipName = "=?utf-8?B?" + base64Encoder.encode(zipName.getBytes(StandardCharsets.UTF_8)) + "?=";
        } else {
            // 其它浏览器
            zipName = URLEncoder.encode(zipName, "utf-8");
        }
        return zipName;
    }



    public void mergePdfWithName(String[] fileIds, String mergeName, HttpServletResponse response) throws IOException {
        //根据不同浏览器处理下载名称
        // 初始化信息
        response.setContentType("application/pdf");
        response.setHeader("Content-disposition", "inline; filename=\"" + URLEncoder.encode(mergeName, StandardCharsets.UTF_8.name())+"\"");
        // Merge multiple PDF files
        List<InputStream> sources = Arrays.stream(fileIds).map(this::getInputStream).collect(Collectors.toList());
        InputStream merge = new PDFMergerUtil().merge(sources);
        org.aspectj.util.FileUtil.copyStream(merge, response.getOutputStream());
    }

    /**
     *  获取原始文件名
     * @param file
     * @return
     */
    private String getOriginalFileName(MultipartFile file) {
        String originalFilename = file.getOriginalFilename();
        if (originalFilename.startsWith("STRBASE64")) {
            String[] strbase64s = originalFilename.split("STRBASE64");
            originalFilename = new String(Base64.decodeBase64(strbase64s[1]));
        }
        return originalFilename;
    }

    @Override
    public InputStream getInputStream(String filePath) {
        return super.getInputStream(filePath);
    }

    public String wordToPdf(WordToPdfDTO wordToPdf) {
        // 判断是否为docx结尾
        String sourceUrl = wordToPdf.getSourceUrl();
//        ToStringUtil.replaceData(jsonObject);
        if (!sourceUrl.endsWith(".docx")) {
            // TODO 提示不为word文档
        }

        FileOutputStream fos = null;
        XWPFDocument doc = null;
        FileInputStream fileInput = null;
        FileInputStream filepdf = null;
        File filePdf = null;
        File fileword = null;

        try {
            Map<String, Object> map = JSONObject.parseObject(JSONObject.toJSONString(wordToPdf.getData()), Map.class);
            doc = WordExportUtil.exportWord07(sourceUrl, map);
            String tempFile = System.getProperty("user.dir") + "\\output";
            File fileDir = new File(tempFile);
            if (!fileDir.exists()) {
                fileDir.mkdirs();
            }
            String fileName = getFileName();
            String tempFileWord = tempFile + "/" + fileName + ".docx";
            String tempFilePdf = tempFile + "/" + fileName + ".pdf";
            fos = new FileOutputStream(tempFileWord);
            doc.write(fos);
            fileInput = new FileInputStream(tempFileWord);
            filePdf = new File(tempFilePdf);
            fileword = new File(tempFileWord);
            toPdf(fileInput, filePdf, SaveFormat.PDF);
            filepdf = new FileInputStream(tempFilePdf);
            MultipartFile multipartFile = new StandardMultipartFilePdf(tempFilePdf, FileCopyUtils.copyToByteArray(filepdf));
            String fileId = uploadFile(multipartFile).getId();
            log.info("filestore.wordToPdf={}",fileId);
            return fileId;
        } catch (Exception e) {
            log.error("execute异常", e);
            throw new ApiException(FileStoreCode.WORD_PDF_ERROR);
        } finally {
            try {
                if (fos != null) {
                    fos.close();
                }
                if (doc != null) {
                    doc.close();
                }
                if (fileInput != null) {
                    fileInput.close();
                }
                if (filepdf != null) {
                    filepdf.close();
                }
                if (filePdf != null && filePdf.exists()) {
                    filePdf.delete();
                }
                if (fileword != null && fileword.exists()) {
                    fileword.delete();
                }
            } catch (Exception e) {
                log.error("文件异常", e);
            }
        }
    }

    public String toWord(WordToPdfDTO wordToPdf) throws IOException {
        String sourceUrl = wordToPdf.getSourceUrl();
//        JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(wordToPdf.getData()));
        XWPFDocument doc = null;
        FileOutputStream fos = null;
        File fileword = null;
        FileInputStream fileInput = null;


//        ToStringUtil.replaceData(jsonObject);
        Map<String, Object> map = JSONObject.parseObject(JSONObject.toJSONString(wordToPdf.getData()), Map.class);

        try {
            doc = WordExportUtil.exportWord07(sourceUrl, map);
            String tempFile = System.getProperty("user.dir") + "\\output";
            File fileDir = new File(tempFile);
            if (!fileDir.exists()) {
                fileDir.mkdirs();
            }
            String fileName = getFileName();
            String tempFileWord = tempFile + "/" + fileName + ".docx";
            fos = new FileOutputStream(tempFileWord);
            doc.write(fos);

            fileInput = new FileInputStream(tempFileWord);
            fileword = new File(tempFileWord);
//            MultipartFile multipartFile = new StandardMultipartFilePdf(tempFileWord, FileCopyUtils.copyToByteArray(fileInput));
            return uploadFile(fileword).getId();
        } catch (Exception e) {

        } finally {
            if (fos != null) {
                fos.close();
            }
            if (doc != null) {
                doc.close();
            }
            if (fileword != null && fileword.exists()) {
                fileword.delete();
            }
        }
        return null;
    }

    public void toPdf(FileInputStream fileInput, File outputFile, int format) {
        InputStream license = FileService.class.getResourceAsStream("/license.xml");
        // 验证License
        if (!getLicense(license)) {
            return;
        }
        FileOutputStream outputStream = null;
        try {
            OsInfo osInfo = SystemUtil.getOsInfo();
            if(osInfo.isLinux()){
                FontSettings fontSettings = new FontSettings();
                fontSettings.setFontsFolder("/usr/share/fonts/win", true);
            }

            Document doc = new Document(fileInput);
            outputStream = new FileOutputStream(outputFile);
            doc.save(outputStream, format);
        } catch (Exception e) {
            log.error("wordToPdf异常", e);
            throw new ApiException(FileStoreCode.WORD_PDF_ERROR);
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
                if (license != null) {
                    license.close();
                }
            } catch (IOException e) {
                log.error("wordToPdf异常", e);

            }
        }
    }

    public boolean getLicense(InputStream license) {
        boolean result = false;
        try {
            License aposeLic = new License();

            aposeLic.setLicense(license);
            result = true;
        } catch (Exception e) {
            log.error("License异常", e);
            throw new ApiException(FileStoreCode.WORD_PDF_ERROR);
        }
        return result;
    }

    private synchronized String getFileName() {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS)) +
                atomicInteger.getAndIncrement();
    }

    public <T> Map<String, Object> beanToMap(T bean) {
        Map<String, Object> map = Maps.newHashMap();
        if (bean != null) {
            BeanMap beanMap = BeanMap.create(bean);
            for (Object key : beanMap.keySet()) {
                map.put(String.valueOf(key), beanMap.get(key));
            }
        }
        return map;
    }


    public FileResult uploadFile(MultipartFile file) throws Exception {
        String filename = file.getOriginalFilename();
        String uploadPath = FileUtil.getUploadPath(filename);
        Map<String, String> headerMap = new HashMap<>();
        headerMap.put("Content-Type", file.getContentType());
        headerMap.put(MinioMetaInfo.ORIGNAL_FILE_NAME, java.util.Base64.getEncoder().encodeToString(filename.getBytes()));
        minioTemplate.putObject(MinioTemplate.DEFAULT_BUCKETNAME, uploadPath, file.getInputStream(), file.getSize(), headerMap);
        return FileResult.builder().fileName(filename).id(uploadPath).build();
    }

    @SuppressWarnings("serial")
    private static class StandardMultipartFilePdf implements MultipartFile, Serializable {

        private String originalFilename;
        private String name;
        private String contentType;
        private final byte[] content;


        public StandardMultipartFilePdf(String originalFilename, byte[] content) {
            this.name = "file";
            this.originalFilename = originalFilename;
            this.content = content;
            this.contentType = "application/pdf";
        }

        @Override
        public String getName() {
            return this.name;
        }

        @Override
        public String getOriginalFilename() {
            return this.originalFilename;
        }

        @Override
        public String getContentType() {
            return this.contentType;
        }

        @Override
        public boolean isEmpty() {
            return (this.content.length == 0);
        }

        @Override
        public long getSize() {
            return this.content.length;
        }

        @Override
        public byte[] getBytes() throws IOException {
            return this.content;
        }

        @Override
        public InputStream getInputStream() throws IOException {
            return new ByteArrayInputStream(this.content);
        }

        @Override
        public void transferTo(File dest) throws IOException, IllegalStateException {
            FileCopyUtils.copy(this.content, dest);
        }

        @Override
        public void transferTo(Path dest) throws IOException, IllegalStateException {
            FileCopyUtils.copy(this.content, Files.newOutputStream(dest));
        }
    }
}
